home *** CD-ROM | disk | FTP | other *** search
- //
- // HenonView:View
- //
- // written by Anders Bertelrud
- // (c) 1990, 1991 by Anders Bertelrud
- //
-
-
- #import "HenonView.h"
- #import <dpsclient/dpsclient.h>
- #import <appkit/appkit.h>
- #import <objc/NXStringTable.h>
-
-
- #define MAXREAL 1e+10
- //#define MAXINT 65535
-
-
- @implementation HenonView
-
-
-
- ////////////////////// Creation & Destruction Methods //////////////////////
-
-
- - initFrame:(const NXRect *)f
- {
- self = [super initFrame:f];
-
- // Create, initialize, and configure a new NXImage (for use as a
- // mapping cache).
- henonCache = [[NXImage alloc] initSize:&f->size];
- [henonCache useCacheWithDepth:NX_TwoBitGrayDepth];
-
- useGrid = YES;
- continuousPlot = YES;
- isRunning = NO;
-
- timerTicking = NO;
-
- // Initialize the default plot values, and init the mapping cache.
- [self setPlotValues1:self];
- [self initHenonCache];
-
- return self;
- }
-
-
-
- - free
- {
- [self stopTimer];
- [henonCache free];
-
- [super free];
- return nil;
- }
-
-
-
- //////////////////////////// Rendering Methods /////////////////////////////
-
-
- - drawSelf:(const NXRect *)rects :(int)rectCount
- {
- // Since the mapping is cached, all we have to do is to composite
- // the cache into the panel.
- [henonCache composite:NX_COPY toPoint:&bounds.origin];
-
- return self;
- }
-
-
-
- /////////////////////////////// File Methods ///////////////////////////////
-
-
- - saveAsTIFF:sender
- {
- id savePanel = [SavePanel new];
- NXStream *tiffStream;
-
- // This method should probably allow the user to specify compression
- // type and (in the case of JPEG) quantization value. This could be
- // done using an accessory view.
- [savePanel setRequiredFileType:"tiff"];
- if ([savePanel runModal])
- {
- if ((tiffStream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) == NULL)
- {
- NXRunAlertPanel([stringTable valueForStringKey:"FS error"],
- [stringTable valueForStringKey:"Couldn't write TIFF to"],
- [stringTable valueForStringKey:"OK"], NULL, NULL,
- [savePanel filename]);
- }
- else
- {
- [henonCache writeTIFF:tiffStream];
- NXSaveToFile(tiffStream, [savePanel filename]);
- NXCloseMemory(tiffStream, NX_FREEBUFFER);
- }
- }
-
- return self;
- }
-
-
-
- ////////////////////////////// Action Methods //////////////////////////////
-
-
- - startStop:sender
- {
- if (!isRunning)
- {
- // We aren't already plotting a map, so start now.
-
- L = [[lField cell] floatValue];
- T = [[tField cell] floatValue];
- R = [[rField cell] floatValue];
- B = [[bField cell] floatValue];
-
- // Now check values and stuff. It is really very bad design philo-
- // sophy to yell at the user using alert panels; this is just a cheap
- // demo, however, so we'll leave it at this for now. At least we're
- // using string tables to make internationalization easy.
-
- if (L >= R)
- {
- NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
- [stringTable valueForStringKey:"Right must be > left"],
- [stringTable valueForStringKey:"OK"], NULL, NULL);
- return self;
- }
-
- if (B >= T)
- {
- NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
- [stringTable valueForStringKey:"Top must be > bottom"],
- [stringTable valueForStringKey:"OK"], NULL, NULL);
- return self;
- }
-
- orbits = [[orbitField cell] intValue];
- if (orbits < 1)
- {
- NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
- [stringTable valueForStringKey:"# orbits must be int >= 1"],
- [stringTable valueForStringKey:"OK"], NULL, NULL);
- return self;
- }
-
- points = [[pointsField cell] intValue];
- if (points < 1)
- {
- NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
- [stringTable valueForStringKey:"# pts/orbit must be int >= 1"],
- [stringTable valueForStringKey:"OK"], NULL, NULL);
- return self;
- }
-
- dy0 = [[incYField cell] floatValue];
- dx0 = [[incXField cell] floatValue];
- y0 = [[startYField cell] floatValue];
- x0 = [[startXField cell] floatValue];
-
- phase = [[phaseField cell] floatValue];
- if (phase < 0 || phase > 3.141593)
- {
- NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
- [stringTable valueForStringKey:"Phase angle must be 0<=a<=pi"],
- [stringTable valueForStringKey:"OK"], NULL, NULL);
- return self;
- }
-
- // Calculate some things used by the caculation algorithm.
- cosa = cos(phase);
- sina = sin(phase);
- xOld = x0;
- yOld = y0;
- xScale = bounds.size.width / (R - L);
- yScale = bounds.size.height / (T - B);
-
- currentOrbit = 1;
- currentPoint = 1;
-
- isRunning = YES;
-
- // Start the plot.
- [[currentOrbitField cell] setIntValue:currentOrbit];
- [self initHenonCache];
- [self display];
-
- // Start the timer.
- [self startTimer];
-
- [nextOrbitButton setEnabled:YES];
- [startStopButton setTitle:[stringTable valueForStringKey:"Stop"]];
- }
- else
- {
- // We're plotting a map, so stop now that the button has been clicked.
-
- isRunning = NO;
- [self stopTimer];
- [nextOrbitButton setEnabled:NO];
- [[currentOrbitField cell] setStringValue:""];
- [startStopButton setTitle:[stringTable valueForStringKey:"Plot"]];
- }
- return self;
- }
-
-
-
- - gridChecked:sender
- {
- useGrid = [sender state];
- return self;
- }
-
-
-
- - nextOrbit:sender
- {
- // This is a quick way to make sure that we'll go into the next orbit
- // the next time doHenonCalculation is called.
- currentPoint = points + 1;
- return self;
- }
-
-
-
- - setPlotValues1:sender
- {
- isRunning = YES; // Hack so that startStop: will do the right thing.
- [self startStop:self]; // Stop the current plot.
-
- phase = 1.111; [phaseField setFloatValue:phase];
- [[phaseSlider cell] setFloatValue:phase];
- L = -1.2; [lField setFloatValue:L];
- T = 1.2; [tField setFloatValue:T];
- R = 1.2; [rField setFloatValue:R];
- B = -1.2; [bField setFloatValue:B];
- x0 = 0.098; [startXField setFloatValue:x0];
- y0 = 0.061; [startYField setFloatValue:y0];
- dx0 = 0.04; [incXField setFloatValue:dx0];
- dy0 = 0.03; [incYField setFloatValue:dy0];
- orbits = 38; [orbitField setIntValue:orbits];
- points = 700; [pointsField setIntValue:points];
-
- return self;
- }
-
-
-
- - setPlotValues2:sender
- {
- isRunning = YES; // Hack so that startStop: will do the right thing.
- [self startStop:self]; // Stop the current plot.
-
- phase = 0.264; [phaseField setFloatValue:phase];
- [[phaseSlider cell] setFloatValue:phase];
- L = -1.2; [lField setFloatValue:L];
- T = 1.2; [tField setFloatValue:T];
- R = 1.2; [rField setFloatValue:R];
- B = -1.2; [bField setFloatValue:B];
- x0 = 0.098; [startXField setFloatValue:x0];
- y0 = 0.061; [startYField setFloatValue:y0];
- dx0 = 0.04; [incXField setFloatValue:dx0];
- dy0 = 0.03; [incYField setFloatValue:dy0];
- orbits = 25; [orbitField setIntValue:orbits];
- points = 300; [pointsField setIntValue:points];
-
- return self;
- }
-
-
-
- - setPlotValues3:sender
- {
- isRunning = YES; // Hack so that startStop: will do the right thing.
- [self startStop:self]; // Stop the current plot.
-
- phase = 1.5732; [phaseField setFloatValue:phase];
- [[phaseSlider cell] setFloatValue:phase];
- L = -3.0; [lField setFloatValue:L];
- T = 3.0; [tField setFloatValue:T];
- R = 3.0; [rField setFloatValue:R];
- B = -3.0; [bField setFloatValue:B];
- x0 = 0.098; [startXField setFloatValue:x0];
- y0 = 0.061; [startYField setFloatValue:y0];
- dx0 = 0.04; [incXField setFloatValue:dx0];
- dy0 = 0.03; [incYField setFloatValue:dy0];
- orbits = 70; [orbitField setIntValue:orbits];
- points = 400; [pointsField setIntValue:points];
-
- return self;
- }
-
-
-
- ////////////////////////////// Timer Methods ///////////////////////////////
-
-
- // The timed entry function.
- void timedEntryFunction (DPSTimedEntry entryID, double currentTime, id self)
- {
- [self doHenonCalculation];
- }
-
-
-
- - startTimer
- {
- [self stopTimer];
- timerTicking = YES;
- timer = DPSAddTimedEntry((double)0.0,
- (DPSTimedEntryProc)&timedEntryFunction, self, NX_BASETHRESHOLD);
- return self;
- }
-
-
-
- - stopTimer
- {
-
- if (timerTicking)
- {
- DPSRemoveTimedEntry(timer);
- timerTicking = NO;
- }
- return self;
- }
-
-
-
- ////////////////////////// Miscellaneous Methods ///////////////////////////
-
-
- - doHenonCalculation
- {
- float xNew, yNew;
-
- // If you want to modify the plot algorithm, this is the place to do it.
- // This method is called continuously (from the timed entry function), and
- // it calculates exactly one point in the Hénon map. The location of the
- // point is based on the location of the previous point as well as on the
- // values given in the plot panel.
- // If, after calculating the current point, we have calculated all the
- // points in the orbit, we update the Hénon window and start the next
- // orbit.
-
- if ([henonCache lockFocus])
- {
- PSsetgray(NX_BLACK);
- if (currentPoint <= points)
- {
- if (abs(xOld) < MAXREAL && abs(yOld) < MAXREAL)
- {
- xNew = xOld*cosa - (yOld - xOld*xOld)*sina;
- yNew = xOld*sina + (yOld - xOld*xOld)*cosa;
-
- if (abs(xNew - L) < MAXINT/xScale
- && abs(T - yNew) < MAXINT/yScale)
- {
- float x, y;
-
- x = (xNew - L) * xScale;
- y = (T - yNew) * yScale;
- if (x >= bounds.origin.x
- && x <= bounds.origin.x + bounds.size.width
- && y >= bounds.origin.y
- && y <= bounds.origin.y + bounds.size.height)
- {
- PSmoveto(x, y);
- PSrlineto(0, 0);
- PSstroke();
- }
- }
-
- xOld = xNew;
- yOld = yNew;
- }
- }
- else
- {
- if (currentOrbit < orbits)
- {
- // Calculate next starting point.
- xOld = x0 + currentOrbit * dx0;
- yOld = y0 + currentOrbit * dy0;
-
- // Reset the current point, and update the TextField in
- // the Hénon panel to reflect this change.
- currentPoint = 1;
- [[currentOrbitField cell] setIntValue:++currentOrbit];
-
- // Update the panel to show the just completed orbit.
- [self lockFocus];
- [henonCache composite:NX_COPY toPoint:&bounds.origin];
- [self unlockFocus];
- [window flushWindow];
- }
- else
- {
- // We're now finished with the plot, so clean things up.
- [self stopTimer];
- isRunning = NO;
- [startStopButton setTitle:"Plot"];
- [nextOrbitButton setEnabled:NO];
- [[currentOrbitField cell] setStringValue:""];
- }
- }
-
- // Set things up so that the next point will be calculated the next
- // time the timed entry calles doHenonCalculation.
- ++currentPoint;
-
- [henonCache unlockFocus];
- }
-
- return self;
- }
-
-
-
- - initHenonCache
- {
- float bR, bT, bL, bB, bW, bH;
- NXPoint origin;
-
- [henonCache lockFocus];
-
- // Figure out shorthands for some edge values.
- bL = bounds.origin.x;
- bT = bounds.origin.y + bounds.size.height;
- bR = bounds.origin.x + bounds.size.width;
- bB = bounds.origin.y;
- bW = bounds.size.width;
- bH = bounds.size.height;
-
- PSsetgray(NX_WHITE);
- NXRectFill(&bounds);
-
- origin.x = (0.0 - L) * bW / (R - L);
- origin.y = (0.0 - B) * bH / (T - B);
-
- if ((R - L) < R && (T - B) < T)
- {
- origin.x = bR - 10;
- origin.x = bB + 10;
- }
-
- // If the user wants a grid, draw one.
- if (useGrid)
- {
- PSmoveto(0, origin.y);
- PSlineto(bR, origin.y);
- PSmoveto(origin.x, 0);
- PSlineto(origin.x, bT);
-
- PSsetgray(NX_DKGRAY);
- PSsetlinewidth(0);
- PSstroke();
- }
-
- [henonCache unlockFocus];
- return self;
- }
-
-
-
- //////////////////////////// Delegation Methods ////////////////////////////
-
-
- - appDidInit:sender
- {
- [window makeKeyAndOrderFront:self];
- return self;
- }
-
-
-
- @end